#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
纯推荐算法实现（独立文件）

包含三种算法：
1) 协同过滤（CollaborativeFiltering）
2) 基于内容（ContentBasedFiltering）
3) 基于流行度（PopularityBasedFiltering）

说明：
- 该文件仅包含算法与打分逻辑，便于复用与单元测试。
- 需要外部提供用户行为读取器（behavior_tracker）与商品读取能力（通过integrated_system.Product）。
"""

from typing import Dict, List, Tuple
from collections import defaultdict

# 依赖于系统内商品读取能力
try:
    from integrated_system import Product
except ImportError:
    # 如果无法导入，尝试从product模块导入
    from product import Product


class CollaborativeFiltering:
    """协同过滤推荐算法（基于用户-商品交互的相似用户推荐）"""

    def __init__(self, behavior_tracker):
        self.behavior_tracker = behavior_tracker

    def calculate_user_similarity(self, user1_id: str, user2_id: str) -> float:
        """计算两个用户的Jaccard相似度（基于购买集合）"""
        user1_purchases = set()
        user2_purchases = set()

        if user1_id in self.behavior_tracker.behaviors["purchases"]:
            user1_purchases = {p["product_id"] for p in self.behavior_tracker.behaviors["purchases"][user1_id]}

        if user2_id in self.behavior_tracker.behaviors["purchases"]:
            user2_purchases = {p["product_id"] for p in self.behavior_tracker.behaviors["purchases"][user2_id]}

        if not user1_purchases or not user2_purchases:
            return 0.0

        intersection = len(user1_purchases & user2_purchases)
        union = len(user1_purchases | user2_purchases)
        return intersection / union if union > 0 else 0.0

    def find_similar_users(self, user_id: str, min_similarity: float = 0.1) -> List[Tuple[str, float]]:
        """找到与目标用户相似的用户（返回 (user_id, similarity) 列表）"""
        similar_users: List[Tuple[str, float]] = []

        all_users = set()
        for behavior_type in self.behavior_tracker.behaviors.values():
            if isinstance(behavior_type, dict):
                all_users.update(behavior_type.keys())

        for other_user_id in all_users:
            if other_user_id != user_id:
                similarity = self.calculate_user_similarity(user_id, other_user_id)
                if similarity >= min_similarity:
                    similar_users.append((other_user_id, similarity))

        return sorted(similar_users, key=lambda x: x[1], reverse=True)

    def recommend_by_collaborative_filtering(self, user_id: str, num_recommendations: int = 10) -> List[str]:
        """给定用户返回推荐的商品ID列表"""
        similar_users = self.find_similar_users(user_id)
        if not similar_users:
            return []

        user_purchases = set()
        if user_id in self.behavior_tracker.behaviors["purchases"]:
            user_purchases = {p["product_id"] for p in self.behavior_tracker.behaviors["purchases"][user_id]}

        recommendations: Dict[str, float] = defaultdict(float)
        for similar_user_id, similarity in similar_users[:10]:
            if similar_user_id in self.behavior_tracker.behaviors["purchases"]:
                for purchase in self.behavior_tracker.behaviors["purchases"][similar_user_id]:
                    pid = purchase["product_id"]
                    if pid not in user_purchases:
                        recommendations[pid] += similarity * purchase.get("quantity", 1)

        sorted_recs = sorted(recommendations.items(), key=lambda x: x[1], reverse=True)
        return [pid for pid, _ in sorted_recs[:num_recommendations]]


class ContentBasedFiltering:
    """基于内容的推荐（通过商品类别、产地、价格相似度打分）"""

    def __init__(self, behavior_tracker):
        self.behavior_tracker = behavior_tracker

    def _similarity(self, p1: Product, p2: Product) -> float:
        score = 0.0
        if p1.kind == p2.kind:
            score += 0.4
        if p1.province == p2.province:
            score += 0.3
        max_price = max(p1.prize, p2.prize)
        if max_price > 0:
            price_similarity = 1 - abs(p1.prize - p2.prize) / max_price
            score += price_similarity * 0.3
        return score

    def recommend_by_content(self, user_id: str, num_recommendations: int = 10) -> List[str]:
        prefs = self.behavior_tracker.get_user_preferences(user_id)
        if not prefs["viewed_products"] and not prefs["purchased_products"]:
            return []

        user_products = prefs["viewed_products"] | prefs["purchased_products"]
        all_products = Product._load_all_products()
        scores: Dict[str, float] = defaultdict(float)

        for upid in user_products:
            up = Product.from_id(upid)
            if not up:
                continue
            for p in all_products:
                if p.id in user_products:
                    continue
                scores[p.id] += self._similarity(up, p)

        sorted_recs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        return [pid for pid, _ in sorted_recs[:num_recommendations]]


class PopularityBasedFiltering:
    """基于流行度的推荐（浏览、购买、评分综合加权）"""

    def __init__(self, behavior_tracker):
        self.behavior_tracker = behavior_tracker

    def calculate_product_popularity(self) -> Dict[str, float]:
        scores: Dict[str, float] = defaultdict(float)

        for user_views in self.behavior_tracker.behaviors.get("views", {}).values():
            for v in user_views:
                scores[v["product_id"]] += 1.0

        for user_purchases in self.behavior_tracker.behaviors.get("purchases", {}).values():
            for p in user_purchases:
                scores[p["product_id"]] += 3.0

        for user_ratings in self.behavior_tracker.behaviors.get("ratings", {}).values():
            for pid, r in user_ratings.items():
                scores[pid] += r.get("rating", 0) * 0.5

        return dict(scores)

    def recommend_by_popularity(self, user_id: str, num_recommendations: int = 10) -> List[str]:
        prefs = self.behavior_tracker.get_user_preferences(user_id)
        seen = prefs["viewed_products"] | prefs["purchased_products"]
        scores = self.calculate_product_popularity()
        filtered = {pid: s for pid, s in scores.items() if pid not in seen}
        sorted_recs = sorted(filtered.items(), key=lambda x: x[1], reverse=True)
        return [pid for pid, _ in sorted_recs[:num_recommendations]]


